---
sidebar_position: 3
---

# 3. Контейнер vector

## Основные сведения

Шаблонный класс `vector` входит в библиотеку STL. Вскоре вы убедитесь, что можно очень много сделать, если знать основные принципы работы класса `vector`, не разбираясь в его реализации.

В классе `vector` поддерживаются динамические массивы. Динамическим массивом называется массив, размеры которого могут увеличиваться по мере необходимости. Класс `vector` оформлен в виде шаблона, что позволяет эффективно использовать его с разными типами. Другими словами, можно создать вектор объектов double, вектор объектов int, вектор объектов `string` и т.д. При помощи шаблона можно создать «класс чего угодно». Чтобы сообщить компилятору, с каким типом данных будет работать класс (в данном случае — какие элементы будут храниться в векторе), укажите имя нужного типа в угловых скобках `<...>`. Так, вектор объектов `string` обозначается `vector<string>`. Такая запись определяет специализированный вектор, в котором могут храниться только объекты string. Если попытаться занести в него объект другого типа, компилятор выдаст сообщение об ошибке.

Поскольку класс `vector` представляет собой "контейнер", то есть предназначается для хранения однотипных элементов, в нем должны быть предусмотрены средства для сохранения и извлечения элементов. Новые элементы добавляются в конец вектора функцией `push_back()` (помните, что эта функция принадлежит классу, поэтому, чтобы вызвать ее для конкретного объекта, нужно отделить ее имя от имени объекта символом точки). Извлечь элементы из вектора можно, используя индексацию: в классе выполнена перегрузка индексации. Благодаря перегрузке операторов программист работает с вектором как с массивом.

Учитывая все сказанное, рассмотрим пример использования векторов. Для этого следует включить в программу заголовочный файл `<vector>`:

```cpp
// Копирование всего содержимого файла в вектор строк
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif
  std::vector<std::string> v;
  std::ifstream in("text.txt");
  std::string line;
  while (getline(in, line))
    v.push_back(line);  // Занесение строки в конец вектора
                        // Нумерация строк:
  for (int i = 0; i < v.size(); i++) {
    std::cout << i << ": " << v[i] << std::endl;
  }
}
```

Результат выполнения программы:

```bash
0: Aaaaddad@lll
1: weqwe231321
2: 123adsad
3: 12312sadasd
4: 3dffdsf
5: asdasdsadasd
```

Мы открываем файл и последовательно читаем его строки в объекты `string`. Объекты заносятся в конец вектора `v`. После завершения цикла `while` все содержимое файла будет находиться в памяти внутри объекта `v`.

Условие проверки цикла `for` означает, что для продолжения работы цикла счетчик i должен быть меньше количества элементов в векторе `v` (количество элементов определяется функцией `size()` класса `vector`).

Откомпилируйте и запустите программу, и вы увидите результат — строки выходного файла окажутся пронумерованными.

Подход оператора `>>` к работе с потоками позволяет легко изменить программу так, чтобы файл разбивался не по строкам, а по словам, разделенным пропусками:

```cpp
// Разбиение файла по словам, разделенный пропусками 
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif
  std::vector<std::string> words;
  std::ifstream in("text.txt");
  std::string word;
  while (in >> word) words.push_back(word);
  for (int i = 0; i < words.size(); i++) {
    std::cout << words[i] << std::endl;
  }
}
```

Результат выполнения программы:

```bash
Aaaaddad@lll
weqwe231321
123adsad
12312sadasd
3dffdsf
asdasdsadasd
```

Следующее выражение обеспечивает ввод очередного "слова": `while (in >> word)`. Когда условие цикла становится ложным, это означает, что был достигнут конец файла.
Чтобы убедиться в том, как просто работать с классом vector, рассмотрим следующий пример, в котором создается вектор с элементами типа `int`:

```cpp
// Создание вектора для хранения целых чисел 
#include <iostream>
#include <vector>

int main() {
  std::vector<int> v;
  for (int i = 0; i < 10; i++) {
    v.push_back(i);
  }
  for (int i = 0; i < v.size(); i++) {
    std::cout << v[i] << ", ";
  }
  std::cout << std::endl;
  for (int i = 0; i < v.size(); i++) {
    v[i] = v[i] * 10;  // Присваивание
  }
  for (int i = 0; i < v.size(); i++) {
    std::cout << v[i] << ", ";
  }
  std::cout << std::endl;
}
```

Результат выполнения программы:

```bash
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 
```

Для класса vector определяются следующие операторы сравнения: `=`, `<`, `<=`, `!=`, `>`, `>=`.

Наиболее важными функциями-членами класса `vector` являются функции `size()`, `begin()`, `end()`, `push_back()`, `insert()` и `erase()`. Функция `size()` возвращает текущий размер вектора. Эта функция особенно полезна, поскольку позволяет узнать размер вектора во время выполнения программы. Помните, вектор может расти по мере необходимости, поэтому размер вектора необходимо определять не в процессе компиляции, а в процессе выполнения программы. Функция `begin()` возвращает итератор начала вектора. Функция `end()` возвращает итератор конца вектора. Как уже говорилось, итераторы очень похожи на указатели и с помощью функций `begin()` и `end()` можно получить итераторы (читай: указатели) начала и конца вектора. Функция `push_back()` помещает значение в конец вектора. Если это необходимо для размещения нового элемента, вектор удлиняется. В середину вектора элемент можно добавить с помощью функции `insert()`. Вектор можно инициализировать. В любом случае, если в векторе хранятся элементы, то с помощью оператора индекса массива к этим элементам можно получить доступ и их изменить. Удалить элементы из вектора можно с помощью функции `erase()`.

## Примеры работы с векторами

### Пример 1

Как вы знаете, в C++ массивы и указатели очень тесно связаны. Доступ к массиву можно получить либо через оператор индексирования, либо через указатель. По аналогии с этим в библиотеке стандартных шаблонов имеется тесная связь между векторами и итераторами. Доступ к членам вектора можно получить либо через оператор индексирования, либо через итератор. В следующем примере показаны оба этих подхода.

```cpp
#include <iostream>
#include <vector>

int main() {
  std::vector<int> v;  // создание вектора нулевой длины
  int i;
  // помещение значений в вектор
  for (i = 0; i < 10; i++) {
    v.push_back(i);
  }
  // доступ к содержимому вектора
  // с использованием оператора индекса
  for (i = 0; i < 10; i++) {
    std::cout << v[i] << " ";
  }
  std::cout << std::endl;
  // доступ к вектору через итератор
  std::vector<int>::iterator p = v.begin();
  while (p != v.end()) {
    std::cout << *p << " ";
    p++;
  }
  std::cout << std::endl;
}
```

Результат выполнения программы:

```bash
0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 5 6 7 8 9
```

В этой программе сначала создается вектор `v` нулевой длины. Далее с помощью функции-члена `push_back()` к концу вектора `v` добавляются некоторые значения и размер вектора `v` увеличивается. Обратите внимание на объявление итератора `р`. Тип `iterator` определяется с помощью класса-контейнера. То есть, чтобы получить итератор для выбранного контейнера, объявить его нужно именно так, как показано в примере: просто укажите перед типом `iterator` имя контейнера. С помощью функции-члена `begin()` итератор инициализируется, указывая на начало вектора. Возвращаемым значением этой функции как раз и является итератор начала вектора. Теперь, применяя к итератору оператор инкремента, можно получить доступ к любому выбранному элементу вектора. Этот процесс совершенно аналогичен использованию указателя для доступа к элементам массива. С помощью функции-члена `end()` определяется факт достижения конца вектора. Возвращаемым значением этой функции является итератор того места, которое находится сразу за последним элементом вектора, Таким образом, если итератор р равен возвращаемому значению функции `v.end()`, значит, конец вектора был достигнут.

### Пример 2

Помимо возможности размещения элементов в конце вектора, с помощью функции-члена `insert()` их можно вставлять в его середину. Удалять элементы из вектора можно с помощью функции-члена `erase()`. В примере представлена демонстрация функций `insert()` и `erase()`.

```cpp
#include <iostream>
#include <vector>

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif
  std::vector<float> v(5, 1);  // создание пятиэлементного вектора из единиц
  // вывод на экран исходных размера и содержимого вектора
  std::cout << "Размер = " << v.size() << std::endl;
  std::cout << "Исходное содержимое:\n";
  for (unsigned int i = 0; i < v.size(); i++) {
    std::cout << v[i] << " ";
  }
  v.clear();
  for (float i = 0; i < 10; i += 1.1) {
    v.push_back(i);
  }
  std::cout << "\nИсходное содержимое:\n";
  for (unsigned int i = 0; i < v.size(); i++) {
    std::cout << v[i] << " ";
  }
  std::vector<float>::iterator p = v.begin();
  p += 2;  // указывает на третий элемент
           // вставка в вектор на то место,
           // куда указывает итератор, десяти новых элементов,
           // каждый из которых равен 9
  v.insert(p, 3, 9);
  // вывод на экран размера
  // и содержимого вектора после вставки
  std::cout << "\n\nРазмер после вставки = " << v.size();
  std::cout << "\nСодержимое после вставки:\n";
  for (unsigned int i = 0; i < v.size(); i++) {
    std::cout << v[i] << " ";
  }
  std::cout << std::endl;
  // удаление вставленных элементов
  p = v.begin();
  p += 2;  // указывает на третий элемент
  v.erase(p, p + 10);  // удаление следующих десяти элементов
                       // за элементом, на который указывает
                       // итератор р
                       // вывод на экран размера
                       // и содержимого вектора после удаления
  std::cout << "\nРазмер после удаления — " << v.size();
  std::cout << "\nСодержимое после удаления:\n";
  for (unsigned int i = 0; i < v.size(); i++) {
    std::cout << v[i] << " ";
  }
  std::cout << std::endl;
}
```

Результат выполнения программы:

```bash
Размер = 5
Исходное содержимое:
1 1 1 1 1 
Исходное содержимое:
0 1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 

Размер после вставки = 13
Содержимое после вставки:
0 1.1 9 9 9 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9 

Размер после удаления — 3
Содержимое после удаления:
0 1.1 9.9 
```

### Пример 3

В следующем примере вектор используется для хранения объектов класса, определённого программистом. Обратите внимание, что в классе определяются конструктор по умолчанию и перегруженные версии операторов `<` и `==`.

```cpp
#include <iomanip>
#include <iostream>
#include <vector>

class Demo {
  double d;

 public:
  Demo() { d = 0.0; }
  Demo(double x) { d = x; }
  Demo &operator-(double x) {
    d = x;
    return *this;
  }
  double getd() { return d; }
  friend bool operator<(Demo a, Demo b) { return a.getd() < b.getd(); }
  friend bool operator==(Demo a, Demo b) { return a.getd() == b.getd(); }
};

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif
  std::vector<Demo> v = {Demo(6.1), Demo(3.9), Demo(2.7), Demo(1.2)};
  unsigned int i;
  std::cout << std::setprecision(3);
  for (i = 0; i < 10; i++) {
    v.push_back(Demo(i / 3.0));
  }
  for (i = 0; i < v.size(); i++) {
    std::cout << v[i].getd() << '\t';
  }
  std::cout << std::endl;
  for (i = 0; i < v.size(); i++) {
    v[i] = v[i].getd() * 2.1;
  }
  for (i = 0; i < v.size(); i++) {
    std::cout << v[i].getd() << '\t';
  }
  std::cout << std::endl;
}
```

Результат выполнения программы:

```bash
6.1     3.9     2.7     1.2     0       0.333   0.667   1       1.33    1.67    2       2.33    2.67    3
12.8    8.19    5.67    2.52    0       0.7     1.4     2.1     2.8     3.5     4.2     4.9     5.6     6.3
```

В этой программе сначала создается вектор `v`, в который сразу записывается четыре объекта класса `Demo`. Затем в вектор в цикле добавляется ещё десять объектов. Следующий цикл выводит на экран содержимое поля `d` каждого объекта, при этом используется метод `getd()`. Ешё следующий цикл меняет значение поля  `d` каждого объекта с помощью индексации и присваивания. И последний цикл снова выводит содержимое вектора на экран.

### Пример 4

Следующий пример – это доработанный [пример 3](#пример-3). Здесь используется функция вывода вектора на экран.

```cpp
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

class Demo {
  double d;

 public:
  Demo() { d = 0.0; }
  Demo(double x) { d = x; }
  Demo& operator-(double x) {
    d = x;
    return *this;
  }
  double getd() { return d; }
  bool operator<(Demo& a) { return getd() < a.getd(); }
  bool operator==(Demo& a) { return getd() == a.getd(); }
};
void print(std::vector<Demo> v) {
  unsigned int i;
  for (i = 0; i < v.size(); i++) {
    std::cout << v[i].getd() << '\t';
  }
}

int main() {
#ifdef WIN32
  system("chcp 65001");
#else
  setlocale(LC_ALL, "Russian");
#endif
  std::vector<Demo> v = {Demo(6.1), Demo(3.9), Demo(2.7), Demo(1.2)};
  unsigned int i;
  std::cout << std::setprecision(3);
  for (i = 0; i < 10; i++) {
    v.push_back(Demo(i / 3.0));
  }
  print(v);
  std::cout << std::endl;
  for (i = 0; i < v.size(); i++) {
    v[i] = v[i].getd() * 2.1;
  }
  print(v);
  std::cout << std::endl;
}

```

Результат выполнения программы:

```bash
6.1     3.9     2.7     1.2     0       0.333   0.667   1       1.33    1.67    2       2.33    2.67    3
12.8    8.19    5.67    2.52    0       0.7     1.4     2.1     2.8     3.5     4.2     4.9     5.6     6.3
```
